/*
 * Copyright 2002-2019 Intel Corporation.
 * 
 * This software is provided to you as Sample Source Code as defined in the accompanying
 * End User License Agreement for the Intel(R) Software Development Products ("Agreement")
 * section 1.L.
 * 
 * This software and the related documents are provided as is, with no express or implied
 * warranties, other than those that are expressly stated in the License.
 */


#include <sstream>
#include "control_manager.H"
#include "control_chain.H"
#include "alarm_manager.H"
#include "controller_events.H"
#include "parse_control.H"

using namespace std;
using namespace CONTROLLER;


/// @cond INTERNAL_DOXYGEN

VOID CONTROL_MANAGER::InitKnobs(){

    _control_knob_family = new KNOB_COMMENT(_control_family,
                                            _family_description);
    _control_knob = new KNOB<string>(KNOB_MODE_APPEND,
        _control_family,
        "control",
        "",
        "Define control events and conditions",
        _prefix);

    _control_log_knob = new KNOB<BOOL>(KNOB_MODE_WRITEONCE,
        _control_family,
        "controller-log",
        "0",
        "Emit the controller's events log",
        _prefix);
    
    _control_log_file_knob = new KNOB<string>(KNOB_MODE_WRITEONCE,
        _control_family,
        "controller-olog",
        "controller-log.txt",
        "file name to emit controller's event log",
        _prefix);

    _control_default_start = new KNOB<BOOL>(KNOB_MODE_WRITEONCE,
        _control_family,
        "controller-default-start",
        "1",
        "add default START event",
        _prefix);

    //this is a hidden internal knob - for debugging
    _control_debug_knob = new KNOB<BOOL>(KNOB_MODE_WRITEONCE,
        "supported",
        "controller-debug",
        "0",
        "debug the controller",
        _prefix);

    _control_skip = new KNOB<string>(KNOB_MODE_WRITEONCE, 
        _control_family,
        "skip",
        "",
        "number of instructions to skip",
        _prefix);

    _control_length = new KNOB<string>(KNOB_MODE_WRITEONCE, 
        _control_family,
        "length",
        "",
        "Number of instructions to execute before stopping",
        _prefix);

    _control_start_address = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "start_address",
        "",
        "Address and count to trigger a start",
        _prefix);

    _control_stop_address = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "stop_address",
        "",
        "Address and count to trigger a start",
        _prefix);

    
    _control_start_ssc = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "start_ssc_mark",
        "",
        "Trigger a start on seeing specified SSC marker -- "
        "big-endian hex  without an 0x prefix",
        _prefix);

    _control_stop_ssc = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "stop_ssc_mark",
        "",
        "Trigger a stop on seeing specified SSC marker -- "
        "big-endian hex  without an 0x prefix",
        _prefix);

    _control_start_itext = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "start-itext",
        "",
        "Trigger a start on seeing specified instruction bytes "
        "(max 15Bytes=30nibbles)",
        _prefix);

    _control_stop_itext = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "stop-itext",
        "",
        "Trigger a stop on seeing specified instruction bytes "
        "(max 15Bytes=30nibbles)",
        _prefix);

    _control_start_int3 = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "start-int3",
        "",
        "Trigger a start on seeing an 'int 3' instruction,"
        "Requires a count argument.",
        _prefix);

    _control_stop_int3 = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "stop-int3",
        "",
        "Trigger a stop on seeing an 'int 3' instruction,"
        "Requires a count argument.",
        _prefix);

    _control_start_isa_ext = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "start_extension",
        "",
        "Trigger a start on seeing an instruction from "
        "this XED ISA extension",
        _prefix);

    _control_stop_isa_ext = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "stop_extension",
        "",
        "Trigger a stop on seeing an instruction from "
        "this XED ISA extension",
        _prefix);

    _control_start_isa_ctg = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "start_category",
        "",
        "Trigger a start on seeing an instruction from "
        "this XED ISA category",
        _prefix);

    _control_stop_isa_ctg = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "stop_category",
        "",
        "Trigger a stop on seeing an instruction from "
        "this XED ISA category",
        _prefix);
#if !defined(TARGET_WINDOWS)
    _control_interactive = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "interactive_file",
        "",
        "name of file used for interactive communication",
        _prefix);
#else
    _control_interactive = new KNOB<string>(KNOB_MODE_APPEND, 
        _control_family,
        "interactive-event",
        "",
        "name of event used for interactive communication",
        _prefix);
#endif
}

CONTROL_MANAGER::CONTROL_MANAGER(const string prefix,
                                 const string family,
                                 const string description)
{
    // The call order must be before restore check point in Replay
    _call_order = CALL_ORDER_FIRST-50 + 2; 
    _late_call_order = CALL_ORDER_LAST + 20;
    _prefix = prefix;
    _control_family = family;
    _family_description = description;
    _uniform_alarm = NULL;
    _interactive_listener = NULL;
    _late_handler = FALSE;
    
    //add knobs definitions(new and old)
    InitKnobs();

    //create the region controller
    CONTROL_ARGS args(_prefix,_control_family);
    _iregions = new CONTROL_IREGIONS(args,this);
    _pcregions = new CONTROL_PCREGIONS(args,this);

    memset(_control_chain_thread_vec,0,sizeof(ADDRINT)*PIN_MAX_THREADS);

    _region_info_callback = NULL;
    _region_info_param = NULL;
}

VOID CONTROL_MANAGER::Fini(INT32, VOID* v){
    CONTROL_MANAGER* mngr = static_cast<CONTROL_MANAGER*>(v);
    if (mngr->_control_log_knob->Value()){
        mngr->_out.close();
    }
}

VOID CONTROL_MANAGER::RegisterHandler(CONTROL_HANDLER ch,
                               VOID* val, BOOL passContext,
                               CONTROL_HANDLER late_ch) 
{
    CONTROL_HANDLER_PARAMS params;
    params.handler = ch;
    params.val = val;
    params.passContext = passContext;
    _control_handler.push_back(params);

    // Add late handler if needed
    // The late handler was added to support precise controller handling in logger
    // Previously logger handled controller events only at the end of a Trace. 
    // Since original handler is called in IPOINT_BEFORE we can not call PIN_ExecuteAt
    // So we have added a late to be called in IPOINT_AFTER and then we call 
    // PIN_ExecuteAt which terminates a Trace and is presice. 
    if (late_ch)
    {
        params.handler = late_ch;
        _late_control_handler.push_back(params);
        if (!_late_handler)
        {
            _late_handler = TRUE;
            // Set late handler flags in alarms
            SetLateHandler();
        }
    }
}


VOID CONTROL_MANAGER::Activate()
{   
    CHAIN_EVENT_VECTOR * controlChains = NULL;

    // Activate IREGION class and get controller knobs from it
    CHAIN_EVENT_VECTOR * regionControlChains = NULL; 
    UINT32 regionControllerNum = 0;
    _iregions->Activate(_pass_context, &regionControlChains);
    if (regionControlChains) {
        regionControllerNum = regionControlChains->size();

    }

    // Activate PCREGION class and get controller knobs from it
    CHAIN_EVENT_VECTOR * pcRegionControlChains = NULL; 
    UINT32 pcRegionControllerNum = 0;
    _pcregions->Activate(_pass_context, &pcRegionControlChains);
    if (pcRegionControlChains) {
        pcRegionControllerNum = pcRegionControlChains->size();
    }

    UINT32 num_chains = _control_knob->NumberOfValues();

    // We do not allow using region controller with other controller knobs
    if (num_chains > regionControllerNum && regionControllerNum > 0) {
        ASSERT(FALSE,"Illegal usage of -control together with -regions:in ");
    }
    if (num_chains > pcRegionControllerNum && pcRegionControllerNum > 0) {
        ASSERT(FALSE,"Illegal usage of -control together with -pcregions:in ");
    }
    if (regionControllerNum > 0 && pcRegionControllerNum > 0) {
        ASSERT(FALSE,"Illegal usage of -regions:intogether with -pcregions:in ");
    }

    if (_control_log_knob->Value()){
        //open the controller event log
        string filename = _control_log_file_knob->Value();
        _out.open(filename.c_str());
        AddIcountInstrumentation();
    }

    //we need to know if context is needed
    _pass_context = ShouldPassContext();

    if (_control_interactive->Value() != ""){

        // We do not allow using region controller with interactive controller 
        if (regionControllerNum > 0 || pcRegionControllerNum) {
            ASSERT(FALSE,"Illegal usage of interactive controller together with -regions:in or -pcregions:in ");
        }
        string name = _control_interactive->Value();
        _interactive_listener = new INTERACTIVE_LISTENER(name);
        _interactive_listener->Active();
        if (_control_log_knob->Value()){
            _out << "Interactive Controller created " << name << endl;
        }
    }

    //iterate all the chains and parse them
    for (UINT32 i=0; i<num_chains; i++){
        string chain_str = _control_knob->Value(i);

        if (_control_log_knob->Value()){
            // Emit the controller knobs
            _out << "Normal Controller knob " << i << " : " << chain_str << endl;
        }

        CONTROL_CHAIN* chain = new CONTROL_CHAIN(this);
        _control_chain.push_back(chain);
        
        //parse the chain - creates all the required alarms
        chain->Parse(chain_str);
        
        if (_control_debug_knob->Value()){
            chain->DebugPrint();
        }
        
        chain->Activate();
    }

    // Check if we got chain handler from iregions or pcregions
    UINT32 num_handler_chains = 0;
    BOOL vector_chain = FALSE;
    if (regionControllerNum) {
        // Support region controller
        num_handler_chains = regionControllerNum;
        controlChains = regionControlChains;
        vector_chain = TRUE;
    }
    else if (pcRegionControllerNum) {
        // Support pc region controller
        num_handler_chains = pcRegionControllerNum;
        controlChains = pcRegionControlChains;
    }
    // Iterate all chains with handles and parse them
    for (UINT32 i=0; i<num_handler_chains; i++){
        string chain_str = (*controlChains)[i].chain_str;
        VOID* event_handler = (*controlChains)[i].event_handler;
       THREADID tid = (*controlChains)[i].tid;

        if (_control_log_knob->Value()){
            // Emit the controller knobs
            _out << "Regions Controller knob " << i << " : " << chain_str << endl;
        }

        CONTROL_CHAIN* chain = new CONTROL_CHAIN(this, event_handler, vector_chain);
        _control_chain.push_back(chain);

        // Add to vector chain of the thread
        if (vector_chain)
        {
            // Search global string in chain str
            // if found that use thread id 0
            THREADID vector_tid = tid;
            if (chain_str.find(":global") != std::string::npos)
            {
                vector_tid = 0;
            }

            // Add check to the vector
            if (_control_chain_thread_vec[vector_tid] == NULL)
                _control_chain_thread_vec[vector_tid] = new CONTROL_CHAIN_VECTOR;
            _control_chain_thread_vec[vector_tid]->push_back(chain);
            chain->SetVectorIndex(_control_chain_thread_vec[vector_tid]->size()-1);
        }

        // parse the chain - creates all the required alarms
        chain->Parse(chain_str);

        if (_control_debug_knob->Value()){
            chain->DebugPrint();
        }

        chain->Activate();
    }

    //add translation of old controller knobs
    if (AddOldKnobs()) {
        // We do not allow using old controller knobs with interactive controller 
        if (regionControllerNum > 0 || pcRegionControllerNum > 0) {
            ASSERT(FALSE,"Illegal usage of old controller knobs together with -regions:in or -pcregions:in ");
        }
    }
    
    //if non of the chains has start event we must add one
    if (_control_default_start->Value()){
        AddDefaultStart();
    }

    PIN_AddFiniFunction(Fini,this);
}

VOID CONTROL_MANAGER::Fire(EVENT_TYPE eventID, CONTEXT* ctxt, 
                           VOID * ip, THREADID tid, BOOL bcast, 
                           VOID* event_handler, CONTROL_CHAIN* chain)
{
    
    if (_control_log_knob->Value()){
        //emit the controllers events log 
        _out << "TID" << tid << ":  event: " << _events.IDToString(eventID) << 
            " at icount: " << _icount[tid] << " ip: " << hexstr(ip) 
            << " handler: " << hexstr(event_handler) << endl;
    }

    // Handle iregion if active
    if (IregionsActive()) {
        _iregions->SetTriggeredRegion(tid,event_handler);
    }

    // Handle PCREGION if active
    if (PCregionsActive()) {
        BOOL legel_event = _pcregions->SetTriggeredRegion(tid,eventID,event_handler);

        // In case we found overlap region we should not fire the event
        if (!legel_event) {
            if (_control_log_knob->Value()){
                //emit the controllers events log 
                _out << "TID" << tid << ":  ignore non legal event: " << _events.IDToString(eventID) << 
                    " at icount: " << _icount[tid] << " ip: " << hexstr(ip) 
                    << " handler: " << hexstr(event_handler) << endl; 
            }

            // Disable more events from this chain
            if (chain)
                chain->SetBlockFire();
            return;
        }
    }

    //call all registered control handlers
    list<CONTROL_HANDLER_PARAMS>::iterator iter = _control_handler.begin();
    for ( ;iter!=_control_handler.end(); iter++){
        iter->handler(eventID, iter->val, ctxt, ip, tid, bcast);
    }
}

// Late Fire event
VOID CONTROL_MANAGER::LateFire(EVENT_TYPE eventID, CONTEXT* ctxt, 
                           VOID * ip, THREADID tid, BOOL bcast)
{
    if (_control_log_knob->Value()){
        //emit the controllers events log 
        _out << "TID" << tid << ":  late Fire event: " << _events.IDToString(eventID) << 
            " at icount: " << _icount[tid] << " ip: " << hexstr(ip) << endl;
    }

    //call all registered control handlers
    list<CONTROL_HANDLER_PARAMS>::iterator iter = _late_control_handler.begin();
    for ( ;iter!=_late_control_handler.end(); iter++){
        iter->handler(eventID, iter->val, ctxt, ip, tid, bcast);
    }
}

//iterate all the control handlers to check if at least one of 
//them needs context
BOOL CONTROL_MANAGER::ShouldPassContext() {

    // If there is a late handler then we need context
    if (_late_handler)
        return TRUE;

    // Check all handlers
    list<CONTROL_HANDLER_PARAMS>::iterator it = _control_handler.begin();
    for (; it != _control_handler.end(); it++)
        if (it->passContext)
            return TRUE;
    return FALSE;
}

VOID CONTROL_MANAGER::AddIcountInstrumentation(){
    memset(_icount,0,sizeof(_icount));
    TRACE_AddInstrumentFunction(Trace, this);
}

VOID CONTROL_MANAGER::Trace(TRACE trace, VOID* v)
{
    for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
    {
        INS ins = BBL_InsHead(bbl);
        INT32 ninst = BBL_NumIns(bbl);
        UINT32 call_order = static_cast<CONTROL_MANAGER*>(v)->_call_order;
        INS_InsertCall(ins, IPOINT_BEFORE,
                            AFUNPTR(ICount),
                            IARG_CALL_ORDER, call_order,
                            IARG_ADDRINT, v,
                            IARG_UINT32, ninst,
                            IARG_THREAD_ID,
                            IARG_END);
    }
}

VOID CONTROL_MANAGER::ICount(CONTROL_MANAGER* control_manager, 
                             UINT32 nins, THREADID thread)
{
    control_manager->_icount[thread] += nins;
}


BOOL CONTROL_MANAGER::AddOldKnobs(){
    // create a "chain" from old controller knobs
    // in case length knob is used we need to append it to the generated chain
    UINT32 added_length = 0;
    added_length += CreateOldOne(_control_skip->Value(),"start","icount",TRUE);
    added_length += CreateOld(_control_start_address,"start","address",TRUE);
    added_length += CreateOld(_control_stop_address,"stop","address");
    added_length += CreateOld(_control_start_ssc,"start","ssc",TRUE);
    added_length += CreateOld(_control_stop_ssc,"stop","ssc");
    added_length += CreateOld(_control_start_itext,"start","itext",TRUE);
    added_length += CreateOld(_control_stop_itext,"stop","itext");
    added_length += CreateOld(_control_start_int3,"start","int3",TRUE);
    added_length += CreateOld(_control_stop_int3,"stop","int3");
    added_length += CreateOld(_control_start_isa_ctg,"start","isa_category",
                              TRUE);
    added_length += CreateOld(_control_stop_isa_ctg,"stop","isa_category");
    added_length += CreateOld(_control_start_isa_ext,"start","isa_extension",
                              TRUE);
    added_length += CreateOld(_control_stop_isa_ext,"stop","isa_extension");
    
    //length knob was used, but no start event was specified - so we did 
    //not add this stop event to any other chain.
    if (_control_length->Value() != ""){
        if (added_length == 0){
            CreateOldOne(_control_length->Value(),"stop","icount");
        }
    }

    return added_length>0;
}

// translate the old controller knob to the new chain syntax
// the knob is expected to be of type APPEND 
// add_length indicates whether we need to append 
// the length knob - in case it is used by the user
UINT32 CONTROL_MANAGER::CreateOld(KNOB<string>* knob, const string& control_event, 
                 const string& alarm, BOOL add_length)
{
    UINT32 length_added = 0;
    for (UINT32 i = 0; i < knob->NumberOfValues(); i++){
        length_added += CreateOldOne(knob->Value(i),control_event,
                                     alarm,add_length);
    }
    return length_added;
}



UINT32 CONTROL_MANAGER::CreateOldOne(const string& raw_knob, 
                                  const string& control_event,
                                  const string& alarm, BOOL add_length)
{
    UINT32 length_added = 0;
    if (raw_knob != ""){
        CONTROL_CHAIN* chain = new CONTROL_CHAIN(this);
        string value;
        string count_str = "";
        string tid_str = "";
        BOOL repeat = FALSE;
        PARSER::ParseOldConfigTokens(raw_knob,value,count_str,tid_str,repeat);
        string config_tokes = "";
        if (count_str != ""){
            config_tokes += ":count" + count_str;
        }
        if (tid_str != ""){
            config_tokes += ":" + tid_str;
        }
        string s = control_event + ":" + alarm + ":" + value + config_tokes;

        if (add_length && _control_length->Value() != ""){
            //length knob is used - append it
            s += ",stop:icount:" + _control_length->Value();
            length_added = 1;
        }
        
        if (repeat){
            s += ",repeat";
        }
        chain->Parse(s);
        _legacy_control_chain.push_back(chain);
        if (_control_debug_knob->Value()){
            chain->DebugPrint();
        }
        chain->Activate();
    }
    return length_added;
}

//check if we have at least one start event
BOOL CONTROL_MANAGER::HasStartEvent(){
    list<CONTROL_CHAIN*>::iterator iter = _control_chain.begin();
    
    for (; iter != _control_chain.end(); iter++){
        CONTROL_CHAIN* chain = *iter;
        if (chain->HasStartEvent()){
            return TRUE;
        }
    }
    
    iter = _legacy_control_chain.begin();
    for (; iter != _legacy_control_chain.end(); iter++){
        CONTROL_CHAIN* chain = *iter;
        if (chain->HasStartEvent()){
            return TRUE;
        }
    }

    if (_iregions->IsActive()){
        return TRUE;
    }

    if (_pcregions->IsActive()){
        return TRUE;
    }

    return FALSE;
}

// Set late handler in chains and alarms
VOID CONTROL_MANAGER::SetLateHandler(){
    list<CONTROL_CHAIN*>::iterator iter = _control_chain.begin();

    for (; iter != _control_chain.end(); iter++){
        CONTROL_CHAIN* chain = *iter;
        chain->SetLateHandler();
    }

    iter = _legacy_control_chain.begin();
    for (; iter != _legacy_control_chain.end(); iter++){
        CONTROL_CHAIN* chain = *iter;
        chain->SetLateHandler();
    }
}

VOID CONTROL_MANAGER::AddDefaultStart(){   
    //no start event was defined.    
    if(HasStartEvent() == FALSE){
        _init_alarm.Activate(this);
    }
}

//find the chain id by the given name
UINT32 CONTROL_MANAGER::GetChainId(const string& chain_name){
    list<CONTROL_CHAIN*>::iterator iter = _control_chain.begin();
    for(; iter != _control_chain.end(); iter++){
        CONTROL_CHAIN* chain = *iter;
        if (chain->GetName() == chain_name){
            return chain->GetId();
        }
    }
    ASSERT(FALSE,"could not find chain name: " + chain_name);
    return 0; //pacify the compiler
}

//find the chain pointer by the Id 
CONTROL_CHAIN* CONTROL_MANAGER::ChainById(UINT32 chain_id){
    list<CONTROL_CHAIN*>::iterator iter = _control_chain.begin();
    for(; iter != _control_chain.end(); iter++){
        CONTROL_CHAIN* chain = *iter;
        if (chain->GetId() == chain_id){
            return chain;
        }
    }
    return NULL;
}

IREGION * CONTROL_MANAGER::CurrentIregion(THREADID tid) const {
    return _iregions->LastTriggeredRegion(tid);
}

PCREGION * CONTROL_MANAGER::CurrentPCregion(THREADID tid) const {
    return _pcregions->LastTriggeredRegion(tid);
}

BOOL CONTROL_MANAGER::IregionsActive() const {
    return _iregions->IsActive();
}

BOOL CONTROL_MANAGER::PCregionsActive() const {
    return _pcregions->IsActive();
}

BOOL CONTROL_MANAGER::UniformActive(){
    return _uniform_alarm != NULL;
}

BOOL CONTROL_MANAGER::UniformIsDone(){
    if (_uniform_alarm){
        return _uniform_alarm->IsUniformDone();
    }
    return FALSE;
}

THREADID CONTROL_MANAGER::GetUniformTid(){
    return _uniform_alarm->GetTid();
}

EVENT_TYPE CONTROL_MANAGER::AddEvent(const string& event_name){
    return _events.AddEvent(event_name);
}

string CONTROL_MANAGER::EventToString(EVENT_TYPE id){
    return _events.IDToString(id);
}

EVENT_TYPE CONTROL_MANAGER::EventStringToType(const string& event_name){
    return _events.EventStringToType(event_name);
}

UINT32 CONTROL_MANAGER::GetInsOrder(){
    return _call_order;
}

UINT32 CONTROL_MANAGER::GetLateInsOrder(){
    return _late_call_order;
}

// Get specific alarm manager according to index
CONTROL_CHAIN * CONTROL_MANAGER::GetNextControlChain(UINT32 index, THREADID tid) {

    ASSERT(_control_chain_thread_vec[tid],"Did not find control chain vector for the thread");

    // Add to vector chain of the thread
    if (index+1 < _control_chain_thread_vec[tid]->size())
    {
        CONTROL_CHAIN_VECTOR * control_chain_vec = _control_chain_thread_vec[tid];
        return (*control_chain_vec)[index+1];
    }

    return NULL;
}
 /// @endcond
